
package combat.dudes.server.protocol.implementation;

import gnu.cajo.invoke.Remote;

import java.rmi.RemoteException;
import java.util.ArrayList;

import combat.dudes.protocol.data.PhysicalObject;
import combat.dudes.protocol.data.RemoteList;
import combat.dudes.protocol.interfaces.OnboardSensorSystemInt;
import combat.dudes.protocol.interfaces.OnboardSystemInt;
import combat.dudes.protocol.interfaces.OnboardWeaponSystemInt;
import combat.dudes.protocol.interfaces.PhysicalObjectInt;
import combat.dudes.protocol.interfaces.ProjectileInt;
import combat.dudes.protocol.interfaces.SimulationInt;
import combat.dudes.protocol.interfaces.TeamInt;
import combat.dudes.protocol.interfaces.TrackInt;
import combat.dudes.protocol.interfaces.UserInt;
import combat.dudes.protocol.interfaces.VehicleInt;
import combat.dudes.protocol.types.OnboardSystemType;
import combat.dudes.server.ServerApp;


/**
 * Probably the most important class of the entire application. Most simulation data is accessible from this class, as the teams are included here, each with their share of data.
 * @author Laurens Andriessen
 * @author Gerard Krombeen
 * @author Erik Meijer
 * @author Jesper Keijzer
 */
public class Simulation implements SimulationInt {

	protected Simulation() {}
	private static Simulation _instance = null;

	private static Double simSpeed = 0.0;
	private static Boolean isRunning = false;
	private static ArrayList<TeamInt> teams = new ArrayList<TeamInt>();

	/**
	 * Return the currently running simulation object. If there is no
	 * simulation: create one, and only one.
	 * @return Simulation
	 */
	// http://radio-weblogs.com/0122027/stories/2003/10/20/implementingTheSingletonPatternInJava.html
	public synchronized static Simulation instance() {
		if(_instance == null) {
			_instance = new Simulation();
		}
		return _instance;
	}

	@Override
	public synchronized Boolean getIsRunning() {
		return isRunning;
	}
	@Override
	public synchronized void setIsRunning(Boolean isRunning) {
		Simulation.isRunning = isRunning;
	}

	@Override
	public synchronized Double getSimSpeed() {
		return simSpeed;
	}
	@Override
	public synchronized void setSimSpeed(Double simSpeed) {
		Simulation.simSpeed = simSpeed;
	}

	@Override
	public synchronized ArrayList<TeamInt> getTeams() {
		return teams;
	}
	@Override
	public synchronized void setTeams(ArrayList<TeamInt> teams) {
		Simulation.teams = teams;
	}
	// Helpers
	@Override
	public synchronized void addTeam(TeamInt team) {
		teams.add(team);
	}
	@Override
	public synchronized void removeTeam(TeamInt team) {
		teams.remove(team);
	}

	// ----------------------------------------------------------------

	/**
	 * Start a new simulation
	 * @author Laurens Andriessen
	 * @return void
	 */
	@Override
	public synchronized final void startSimulation() {
		// Java Code to START a new simulation
		// Probably setSpeed = 1.0 and isRunning = true?
		// TODO add checks if it's allowed to start a simulation!
		setSimSpeed(1.0);
		setIsRunning(true);
	}

	/**
	 * Stop the current simulation
	 * @author Laurens Andriessen
	 * @return void
	 */
	@Override
	public synchronized final void stopSimulation() {
		// Java Code to STOP the current simulation
		// Probably setSpeed = 0.0 and isRunning = false?
		setSimSpeed(0.0);
		setIsRunning(false);
		// TODO remove all objects and information, add to log, etc.
	}

	/**
	 * Pause the current simulation
	 * @author Laurens Andriessen
	 * @return void
	 */
	@Override
	public synchronized final void pauseSimulation() {
		// Java Code to PAUSE the current simulation
		// Probably setSpeed = 0.0 and isRunning = true?
		setSimSpeed(0.0);
		setIsRunning(true);
		// TODO Is the simulation even started? Write to log.
	}

	/**
	 * Sends back a list of PhysicalObjects to the client containing all ships,
	 * particles, etc.
	 * @author Laurens Andriessen
	 * @author Jesper Keijzer
	 * @return ArrayList<PhysicalObjectImp>
	 */
	@Override
	public synchronized final ArrayList<PhysicalObjectInt> update(TeamInt team) {
		team = findTeam(team);
		ArrayList<PhysicalObjectInt> physObjects = new ArrayList<PhysicalObjectInt>();

		for(TeamInt selectTeam : teams) {
			if(selectTeam.equals(team) || (team.getTeamID() == 1)) {
				// Add all PhysicalObjects of the team in parameter in the ArrayList
				for(VehicleInt vehicle : selectTeam.getVehicles())
					physObjects.add(vehicle);
				for(ProjectileInt projectile : selectTeam.getProjectiles())
					physObjects.add(projectile);
				for(TrackInt track : selectTeam.getTracks())
					physObjects.add(track);
			} else {
				// Get all PhysicalObjects of all other teams and calculate if the team in can see them before adding them to the ArrayList
				for(VehicleInt vehicle : team.getVehicles())
					for(OnboardSystemInt onboardSystem : vehicle.getOnboardSystems())
						if(onboardSystem.getOnboardSystemType() == OnboardSystemType.SENSOR)
							for(PhysicalObject detection : ((OnboardSensorSystem) onboardSystem).scan())
								if(!physObjects.contains(detection))
									physObjects.add(detection);
			}
		}
		return physObjects;
	}
	@Override
	public synchronized UserInt userLogOn(String userName) {
		User newUser = new User(userName);
		teams.get(0).addUser(newUser);
		ServerApp.Log("User '" + newUser.getUserName() + "' (ID " + newUser.getUserID() + ") logged in succesfully");
		return newUser;
	}

	@Override
	public synchronized TeamInt findTeamOfUser(UserInt user) {
		if(user != null) {
			user = findUser(user);

			for(TeamInt team : getTeams()) {
				for(UserInt u : team.getUsers())
					if(u.getUserID() == user.getUserID())
						return team;
			}
			ServerApp.Log("Can't find user '" + user.getUserName() + "' in any team @ findTeamOfUser()");
		}
		ServerApp.Log("Can't find user if parameter is null @ findTeamOfUser()");
		return null;
	}

	@Override
	public synchronized TeamInt switchTeam(UserInt user, TeamInt team) {
		user = findUser(user);
		team = findTeam(team);

		if(user != null) {
			if(team != null) {
				try {
					synchronized(user) {
						TeamInt oldTeam = findTeamOfUser(user);
						team.addUser(user);
						oldTeam.removeUser(findUser(user));
						ServerApp.Log("User '" + user.getUserName() + "' has been moved from team '" + oldTeam.getTeamName() + "' to team '" + findTeamOfUser(user).getTeamName() + "'");
					}
					return findTeamOfUser(user);
				} catch(Exception e) {
					ServerApp.onError("Switching user failed ('" + user.getUserName() + "' to '" + team.getTeamName() + "')", e, false);
				}
			} else ServerApp.Log("Switching users failed, couldn't find team");
		} else ServerApp.Log("Switching users failed, couldn't find user");

		return null;
	}

	@Override
	public RemoteList getTeams_Remote() {
		try {
			return new RemoteList(getTeams());
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, getTeams_Remote()", e, false);
		}
		return new RemoteList();
	}
	@Override
	public Object userLogOn_Remote(String userName) {
		try {
			return new Remote(userLogOn(userName));
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, userLogOn_Remote()", e, false);
		}
		return null;
	}
	@Override
	public Object findTeamOfUser_Remote(UserInt user) {
		try {
			return new Remote(findTeamOfUser(user));
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, findTeamOfUser_Remote()", e, false);
		}
		return null;
	}
	@Override
	public Object switchTeam_Remote(UserInt user, TeamInt team) {
		try {
			return new Remote(switchTeam(user, team));
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, switchTeam_Remote()", e, false);
		}
		return null;
	}
	@Override
	public RemoteList update_Remote(TeamInt team) {
		try {
			return new RemoteList(update(team));
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, update_Remote()", e, false);
		}
		return new RemoteList();
	}

	public UserInt findUser(UserInt user) {
		for(TeamInt t : getTeams())
			for(UserInt u : t.getUsers())
				if(u.getUserID() == user.getUserID())
					return u;
		return null;
	}

	public TeamInt findTeam(TeamInt team) {
		for(TeamInt t : getTeams())
			if(t.getTeamID() == team.getTeamID())
				return t;
		return null;
	}

	public PhysicalObjectInt findPhysicalObject(Object object) {
		PhysicalObjectInt obj = (PhysicalObjectInt) object;
		if(obj != null) {
			for(TeamInt t : getTeams()) {
				switch(obj.getType()) {
					case UNKNOWN:
						return obj;

					case VEHICLE:
						for(VehicleInt vehicle : t.getVehicles())
							if(vehicle.getObjID() == obj.getObjID())
									return vehicle;
						break;

					case PROJECTILE:
						for(ProjectileInt projectile : t.getProjectiles())
							if(projectile.getObjID() == obj.getObjID())
									return projectile;
						break;

					case TRACK:
						for(TrackInt track : t.getTracks())
							if(track.getObjID() == obj.getObjID())
									return track;
						break;
				}
			}
		}
		return null;
	}
	public Object findPhysicalObject_Remote(Object object) {
		try {
			return new Remote(findPhysicalObject(object));
		} catch(RemoteException e) {
			ServerApp.onError("@Simulation.java, findPhysicalObject_Remote()", e, false);
		}
		return null;
	}

	public OnboardWeaponSystemInt findWeaponSystem(OnboardWeaponSystemInt system) {
		for(TeamInt t : getTeams())
			for(VehicleInt v : t.getVehicles())
				for(OnboardSystemInt s : v.getOnboardSystems())
					if(s.getOnboardSystemType() == OnboardSystemType.WEAPON)
						if(s.getSystemID() == system.getSystemID()) {
							System.out.println(s.getClass());
							return (OnboardWeaponSystemInt) s;
						}
		return null;
	}
	public OnboardSensorSystemInt findWeaponSystem(OnboardSensorSystemInt system) {
		for(TeamInt t : getTeams())
			for(VehicleInt v : t.getVehicles())
				for(OnboardSystemInt s : v.getOnboardSystems())
					if(s.getOnboardSystemType() == OnboardSystemType.SENSOR)
						return (OnboardSensorSystemInt) s;
		return null;

	}

}
